/**
* \file DGVBlitzArrayCasterVTK.h
*
* \brief This file defines the class for casting VTK objects to and from Blitz++ Arrays.
* \author Shekhar S. Chandra, 2008-2009
*
* This file is part of DGV Library.
*
* DGV is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* DGV is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with DGV.  If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef DGVBLITZARRAYCASTERVTK_H
#define DGVBLITZARRAYCASTERVTK_H

#include <vtkTransform.h>
#include <vtkImageChangeInformation.h>
#include <vtkImageReslice.h>

#include "vtkImageData.h"

#include "DGVAliases.h"

/**
    \class DGVBlitzArrayCasterVTK
    \brief Object for casting to and from Blitz ++ Arrays involving VTK objects.
    \author Shekhar S. Chandra, 2008-2009
    \todo Check the alignment of x,y and z axis.

    Casts from and to Blitz++ Arrays and vtkImageData objects. Stores the min, max and mean of the data also.
*/
template<typename type>
class DGV_VTK_TEMPLATEDLL DGVBlitzArrayCasterVTK
{
public:
    DGVBlitzArrayCasterVTK();
    virtual ~DGVBlitzArrayCasterVTK();

    /**
        \fn DGVBlitzArrayCasterVTK::arrayToVTKImageData(Array<type,2> &data)
        \brief Cast/Convert a Blitz ++ Array to a VTK Image Data object.
        Returns a pointer to a newly constructed vtkImageData object containing the
        casted data.
    */
    vtkImageData* arrayToVTKImageData(Array<type,2> &data);
    vtkImageData* arrayToVTKImageData_UnsignedChar(Array<type,2> &data);
    vtkImageData* arrayToVTKImageData(Array<type,3> &data);
    vtkImageData* arrayToVTKImageData_UnsignedChar(Array<type,3> &data);
    vtkImageData* arrayToVTKImageData(Array<complex<type>,3> &data);

    /**
        \fn DGVBlitzArrayCasterVTK::vtkImageDataToArray(vtkImageData *data, Array<type,2> &newArray)
        \brief Cast/Convert a VTK Image Data object to a Blitz ++ Array.
        Assigns the vtkImageData to supplied Blitz ++ Array.
    */
    bool vtkImageDataToArray(vtkImageData *data, Array<type,2> &newArray);
    bool vtkImageDataToArray(vtkImageData *data, int slice, Array<type,2> &newArray);
    bool vtkImageDataToArray(vtkImageData *data, Array<type,3> &newArray);

    /**
        \fn DGVBlitzArrayCasterVTK::convertCoordinatesToArray(vtkImageData *&data)
        \brief Converts coordinates from VTK to Blitz++ Arrays. DEPRECATED.
        CAUSES DISLOCATION ARTEFACTS DUE TO ROTATION DONE!
    */
    void convertCoordinatesToArray(vtkImageData *&data);

    /**
        \fn DGVBlitzArrayCasterVTK::alignmentForImages(bool forImages)
        \brief Array axis alignment for image display and not standard right-handed coordinate system which is default.
        Image axis alignment has the x and y coordinates switched and y values reversed.
    */
    inline void alignmentForImages(bool forImages)
    {   imageAlignment = forImages; }

    inline type getMin()
    {   return min; }
    inline type getMax()
    {   return max; }
    inline type getMean()
    {   return mean;    }

protected:
    bool imageAlignment; //!< Image alignment flag
    type min; //!< Minimum image value
	type max; //!< Maximum image value
	type mean; //!< Mean image value
};

template<typename type>
DGVBlitzArrayCasterVTK<type>::DGVBlitzArrayCasterVTK()
{
    imageAlignment = false;
}

template<typename type>
DGVBlitzArrayCasterVTK<type>::~DGVBlitzArrayCasterVTK()
{
    //dtor
}

template<typename type>
vtkImageData* DGVBlitzArrayCasterVTK<type>::arrayToVTKImageData(Array<type,2> &data)
{
    vtkImageData *imageData = vtkImageData::New();
    type tmp;
    numeric_limits< type > typeLimits;

    min = typeLimits.max();
    max = typeLimits.min();
    mean = 0;

    imageData->SetScalarTypeToDouble();
    imageData->SetNumberOfScalarComponents(1);

    if(imageAlignment)
    {
        int dimensions[3] = {data.cols(),data.rows(),0}, extents[6] = {0,data.cols()-1,0,data.rows()-1,0,0};

        imageData->SetDimensions(dimensions);
        imageData->SetExtent(extents);

        for(int j = 0; j < data.rows(); j ++)
            for(int k = 0; k < data.cols(); k ++)
            {
                tmp = static_cast<type>(data(j,k));
                if(tmp < min)
                    min = tmp;
                else if(tmp > max)
                    max = tmp;
                imageData->SetScalarComponentFromDouble(k,data.rows()-1-j,0,0,tmp);
                mean += tmp;
            }
    }
    else
    {
        int dimensions[3] = {data.rows(),data.cols(),0}, extents[6] = {0,data.rows()-1,0,data.cols()-1,0,0};

        imageData->SetDimensions(dimensions);
        imageData->SetExtent(extents);

        for(int j = 0; j < data.rows(); j ++)
            for(int k = 0; k < data.cols(); k ++)
            {
                tmp = static_cast<type>(data(j,k));
                if(tmp < min)
                    min = tmp;
                else if(tmp > max)
                    max = tmp;
                imageData->SetScalarComponentFromDouble(j,k,0,0,tmp);
                mean += tmp;
            }
    }

    mean /= data.rows()*data.cols();
    imageData->Update();

    return imageData;
}

template<typename type>
vtkImageData* DGVBlitzArrayCasterVTK<type>::arrayToVTKImageData_UnsignedChar(Array<type,2> &data)
{
    vtkImageData *imageData = vtkImageData::New();
    type tmp;
    numeric_limits< type > typeLimits;

    min = typeLimits.max();
    max = typeLimits.min();
    mean = 0;

    imageData->SetScalarTypeToUnsignedChar();
    imageData->SetNumberOfScalarComponents(1);

    if(imageAlignment)
    {
        int dimensions[3] = {data.cols(),data.rows(),0}, extents[6] = {0,data.cols()-1,0,data.rows()-1,0,0};

        imageData->SetDimensions(dimensions);
        imageData->SetExtent(extents);

        for(int j = 0; j < data.rows(); j ++)
            for(int k = 0; k < data.cols(); k ++)
            {
                tmp = static_cast<type>(data(j,k));
                if(tmp < min)
                    min = tmp;
                else if(tmp > max)
                    max = tmp;
                imageData->SetScalarComponentFromDouble(k,data.rows()-1-j,0,0,tmp);
                mean += tmp;
            }
    }
    else
    {
        int dimensions[3] = {data.rows(),data.cols(),0}, extents[6] = {0,data.rows()-1,0,data.cols()-1,0,0};

        imageData->SetDimensions(dimensions);
        imageData->SetExtent(extents);

        for(int j = 0; j < data.rows(); j ++)
            for(int k = 0; k < data.cols(); k ++)
            {
                tmp = static_cast<type>(data(j,k));
                    if(tmp < min)
                        min = tmp;
                    else if(tmp > max)
                        max = tmp;
                imageData->SetScalarComponentFromDouble(j,k,0,0,tmp);
                mean += tmp;
            }
    }
    mean /= data.rows()*data.cols();
    imageData->Update();

    return imageData;
}

template<typename type>
vtkImageData* DGVBlitzArrayCasterVTK<type>::arrayToVTKImageData(Array<type,3> &data)
{
    vtkImageData *imageData = vtkImageData::New();
    type tmp;
    numeric_limits< type > typeLimits;

    min = typeLimits.max();
    max = typeLimits.min();
    mean = 0;

    imageData->SetScalarTypeToDouble();
    imageData->SetNumberOfScalarComponents(1);

    if(imageAlignment)
    {
        int dimensions[3] = {data.cols(),data.rows(),data.depth()}, extents[6] = {0,data.cols()-1,0,data.rows()-1,0,data.depth()-1};

        imageData->SetDimensions(dimensions);
        imageData->SetExtent(extents);

        for(int j = 0; j < data.rows(); j ++)
            for(int k = 0; k < data.cols(); k ++)
                for(int l = 0; l < data.depth(); l ++)
                {
                    tmp = static_cast<type>(data(j,k,l));
                    if(tmp < min)
                        min = tmp;
                    else if(tmp > max)
                        max = tmp;
                    imageData->SetScalarComponentFromDouble(k,data.rows()-1-j,l,0,tmp);
                    mean += tmp;
                }
    }
    else
    {
        int dimensions[3] = {data.rows(),data.cols(),data.depth()}, extents[6] = {0,data.rows()-1,0,data.cols()-1,0,data.depth()-1};

        imageData->SetDimensions(dimensions);
        imageData->SetExtent(extents);

        for(int j = 0; j < data.rows(); j ++)
            for(int k = 0; k < data.cols(); k ++)
                for(int l = 0; l < data.depth(); l ++)
                {
                    tmp = static_cast<type>(data(j,k,l));
                    if(tmp < min)
                        min = tmp;
                    else if(tmp > max)
                        max = tmp;
                    imageData->SetScalarComponentFromDouble(j,k,l,0,tmp);
                    mean += tmp;
                }
    }
    mean /= data.rows()*data.cols()*data.depth();
    imageData->Update();

    return imageData;
}

template<typename type>
vtkImageData* DGVBlitzArrayCasterVTK<type>::arrayToVTKImageData_UnsignedChar(Array<type,3> &data)
{
    vtkImageData *imageData = vtkImageData::New();
    type tmp;
    numeric_limits< type > typeLimits;

    min = typeLimits.max();
    max = typeLimits.min();
    mean = 0;

    imageData->SetScalarTypeToUnsignedChar();
    imageData->SetNumberOfScalarComponents(1);

    if(imageAlignment)
    {
        int dimensions[3] = {data.cols(),data.rows(),data.depth()}, extents[6] = {0,data.cols()-1,0,data.rows()-1,0,data.depth()-1};

        imageData->SetDimensions(dimensions);
        imageData->SetExtent(extents);

        for(int j = 0; j < data.rows(); j ++)
            for(int k = 0; k < data.cols(); k ++)
                for(int l = 0; l < data.depth(); l ++)
                {
                    tmp = static_cast<type>(data(j,k,l));
                    if(tmp < min)
                        min = tmp;
                    else if(tmp > max)
                        max = tmp;
                    imageData->SetScalarComponentFromDouble(k,data.rows()-1-j,l,0,tmp);
                    mean += tmp;
                }
    }
    else
    {
        int dimensions[3] = {data.rows(),data.cols(),data.depth()}, extents[6] = {0,data.rows()-1,0,data.cols()-1,0,data.depth()-1};

        imageData->SetDimensions(dimensions);
        imageData->SetExtent(extents);

        for(int j = 0; j < data.rows(); j ++)
            for(int k = 0; k < data.cols(); k ++)
                for(int l = 0; l < data.depth(); l ++)
                {
                    tmp = static_cast<type>(data(j,k,l));
                    if(tmp < min)
                        min = tmp;
                    else if(tmp > max)
                        max = tmp;
                    imageData->SetScalarComponentFromDouble(j,k,l,0,tmp);
                    mean += tmp;
                }
    }
    mean /= data.rows()*data.cols()*data.depth();
    imageData->Update();

    return imageData;
}

template<typename type>
vtkImageData* DGVBlitzArrayCasterVTK<type>::arrayToVTKImageData(Array<complex<type>,3> &data)
{
    vtkImageData *imageData = vtkImageData::New();
    type tmp1, tmp2, tmp3;
    numeric_limits< type > typeLimits;

    min = typeLimits.max();
    max = typeLimits.min();
    mean = 0;

    imageData->SetScalarTypeToDouble();
    imageData->SetNumberOfScalarComponents(3);

    if(imageAlignment)
    {
        int dimensions[3] = {data.cols(),data.rows(),data.depth()}, extents[6] = {0,data.cols()-1,0,data.rows()-1,0,data.depth()-1};

        imageData->SetDimensions(dimensions);
        imageData->SetExtent(extents);

        for(int j = 0; j < data.rows(); j ++)
            for(int k = 0; k < data.cols(); k ++)
                for(int l = 0; l < data.depth(); l ++)
                {
                    tmp1 = static_cast<type>( abs(data(j,k,l)) );
                    tmp2 = static_cast<type>( data(j,k,l).real() );
                    tmp3 = static_cast<type>( data(j,k,l).imag() );
                    if(tmp1 < min)
                        min = tmp1;
                    else if(tmp1 > max)
                        max = tmp1;
                    imageData->SetScalarComponentFromDouble(k,data.rows()-1-j,l,0,tmp1);
                    imageData->SetScalarComponentFromDouble(k,data.rows()-1-j,l,1,tmp2);
                    imageData->SetScalarComponentFromDouble(k,data.rows()-1-j,l,2,tmp3);
                    mean += tmp1;
                }
    }
    else
    {
        int dimensions[3] = {data.rows(),data.cols(),data.depth()}, extents[6] = {0,data.rows()-1,0,data.cols()-1,0,data.depth()-1};

        imageData->SetDimensions(dimensions);
        imageData->SetExtent(extents);

        for(int j = 0; j < data.rows(); j ++)
            for(int k = 0; k < data.cols(); k ++)
                for(int l = 0; l < data.depth(); l ++)
                {
                    tmp1 = static_cast<type>( abs(data(j,k,l)) );
                    tmp2 = static_cast<type>( data(j,k,l).real() );
                    tmp3 = static_cast<type>( data(j,k,l).imag() );
                    if(tmp1 < min)
                        min = tmp1;
                    else if(tmp1 > max)
                        max = tmp1;
                    imageData->SetScalarComponentFromDouble(j,k,l,0,tmp1);
                    imageData->SetScalarComponentFromDouble(j,k,l,1,tmp2);
                    imageData->SetScalarComponentFromDouble(j,k,l,2,tmp3);
                    mean += tmp1;
                }
    }
    mean /= data.rows()*data.cols()*data.depth();
    imageData->Update();

    return imageData;
}

template<typename type>
bool DGVBlitzArrayCasterVTK<type>::vtkImageDataToArray(vtkImageData *data, Array<type,2> &newArray)
{
    int bounds[6];
	type tmp;
    numeric_limits< type > typeLimits;

    min = typeLimits.max();
    max = typeLimits.min();
    mean = 0;

    data->GetExtent(bounds);

    int boundX = bounds[1]+1, boundY = bounds[3]+1, boundZ = bounds[5]+1;

    if(boundX < 0 || boundY < 0 || boundZ < 0) //Bounds values are zero-indexed
        return false;

    int rows = boundX-bounds[0], cols = boundY-bounds[2];

    if(imageAlignment)
        newArray.resizeAndPreserve(cols,rows); ///The coordinates are 90 degrees (anti-clockwise) different to VTK.
    else
        newArray.resizeAndPreserve(rows,cols); ///The coordinates are 90 degrees (anti-clockwise) different to VTK.

    for(int j = bounds[0]; j < rows; j ++) ///\todo Check for correct bounds checking here
        for(int k = bounds[2]; k < cols; k ++)
        {
            tmp = static_cast<type>(data->GetScalarComponentAsDouble(j,k,0,0));

            if(tmp < min)
                min = tmp;
            else if(tmp > max)
                max = tmp;

            if(imageAlignment)
                newArray(cols-1-k,j) = tmp;
            else
                newArray(j,k) = tmp;

            mean += tmp;
        }

    mean /= newArray.rows()*newArray.cols();
    return true;
}

template<typename type>
bool DGVBlitzArrayCasterVTK<type>::vtkImageDataToArray(vtkImageData *data, int slice, Array<type,2> &newArray)
{
    int bounds[6];
	type tmp;
    numeric_limits< type > typeLimits;

    min = typeLimits.max();
    max = typeLimits.min();
    mean = 0;

    data->GetExtent(bounds);

    int boundX = bounds[1]+1, boundY = bounds[3]+1, boundZ = bounds[5]+1;
    int rows = boundX-bounds[0], cols = boundY-bounds[2], depth = boundZ-bounds[4];

    if(boundX < 0 || boundY < 0 || boundZ < 0) //Bounds values are zero-indexed
        return false;
    else if(slice > depth || slice < 0)
        return false;

    if(imageAlignment)
        newArray.resizeAndPreserve(cols,rows); ///The coordinates are 90 degrees (anti-clockwise) different to VTK.
    else
        newArray.resizeAndPreserve(rows,cols); ///The coordinates are 90 degrees (anti-clockwise) different to VTK.

    for(int j = bounds[0]; j < rows; j ++) ///\todo Check for correct bounds checking here
        for(int k = bounds[2]; k < cols; k ++)
        {
            tmp = static_cast<type>(data->GetScalarComponentAsDouble(j,k,slice,0));

            if(tmp < min)
                min = tmp;
            else if(tmp > max)
                max = tmp;

            if(imageAlignment)
                newArray(cols-1-k,j) = tmp;
            else
                newArray(j,k) = tmp;

            mean += tmp;
        }

    mean /= newArray.rows()*newArray.cols();
    return true;
}

template<typename type>
bool DGVBlitzArrayCasterVTK<type>::vtkImageDataToArray(vtkImageData *data, Array<type,3> &newArray)
{
    int bounds[6];
	type tmp;
    numeric_limits< type > typeLimits;

    min = typeLimits.max();
    max = typeLimits.min();
    mean = 0;

    data->GetExtent(bounds);

    int boundX = bounds[1]+1, boundY = bounds[3]+1, boundZ = bounds[5]+1;

    if(boundX < 0 || boundY < 0 || boundZ < 0) //Bounds values are zero-indexed
        return false;

    int rows = boundX-bounds[0], cols = boundY-bounds[2], depth = boundZ-bounds[4];

    if(imageAlignment)
        newArray.resizeAndPreserve(cols,rows,depth); ///The coordinates are 90 degrees (anti-clockwise) different to VTK.
    else
        newArray.resizeAndPreserve(rows,cols,depth); ///The coordinates are 90 degrees (anti-clockwise) different to VTK.

    for(int j = bounds[0]; j < rows; j ++)
        for(int k = bounds[2]; k < cols; k ++)
            for(int l = bounds[4]; l < depth; l ++)
            {
                tmp = static_cast<type>(data->GetScalarComponentAsDouble(j,k,l,0));

                if(tmp < min)
                    min = tmp;
                else if(tmp > max)
                    max = tmp;

                if(imageAlignment)
                    newArray(cols-1-k,j,l) = tmp;
                else
                    newArray(j,k,l) = tmp;

                mean += tmp;
            }

    mean /= newArray.rows()*newArray.cols()*newArray.depth();
    return true;
}

template<typename type>
void DGVBlitzArrayCasterVTK<type>::convertCoordinatesToArray(vtkImageData *&data)
{
    int bounds[6];
    vtkTransform *transform = vtkTransform::New();
    vtkImageReslice *reslice = vtkImageReslice::New();
    vtkImageChangeInformation *info = vtkImageChangeInformation::New();

    //This method for rotating data creates dislocation artefacts in vtkImageData
    transform->RotateZ(90);
    data->GetExtent(bounds);
    info->SetInput(data);
    info->CenterImageOn();

    double center_x = (bounds[1])/2.0, center_y = (bounds[3])/2.0;
    reslice->SetInputConnection(info->GetOutputPort());
    reslice->SetInformationInput(data);
    reslice->SetResliceTransform(transform);
    reslice->SetOutputDimensionality(2);
    reslice->SetOutputOrigin(-center_x,-center_y,0);
    reslice->InterpolateOff();
    reslice->Update();

    data->Delete();
    data = reslice->GetOutput(); ///\todo Leak here?
    transform->Delete();
    info->Delete();

    data->Update();
}

#endif // DGVBLITZARRAYCASTERVTK_H
